package model;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class Ll1Checker {
	private Map<String, Rule> rules;
	private NonTerminal startNonTerminal;
	private List<NonTerminal> nonTerminals;

	private boolean conflict1;
	private boolean conflict2;
	private boolean conflict3;
	
	private String conflict1Message;
	private String conflict2Message;
	private String conflict3Message;
	
	public Ll1Checker(Map<String, Rule> rules, NonTerminal startNonTerminal) {
		this.rules = rules;
		this.startNonTerminal = startNonTerminal;
		nonTerminals = new LinkedList<NonTerminal>();

		conflict1 = false;
		conflict2 = false;
		conflict3 = false;
		
		conflict1Message = null;
		conflict2Message = null;
		conflict3Message = null;
		
		getNonTerminals();
		computeStarters();
		computeFollowers();

		showStarters();
		showFollowers();
		
		checkStarterStarterConflict();
		checkStarterFollowConflict();
		checkLeftRecursiveConflict();
		
		if(conflict1 || conflict2 || conflict3) {
			System.out.println("BNF voldoet niet aan de eisen");
			
			if(conflict1) {
				System.out.println("Conflict 1: "+ conflict1Message);
			}
			
			if(conflict2) {
				System.out.println("Conflict 2: "+ conflict2Message);
			}
			
			if(conflict3) {
				System.out.println("Conflict 3: "+ conflict3Message);
			}
		} else {
			System.out.println("Bnf voldoet aan alle 3 de regels");
		}
	}

	private void getNonTerminals() {
		for(Rule rule : rules.values()) {
			nonTerminals.add(rule.getRuleName());
		}
	}

	private void computeStarters() {
		for(NonTerminal nonTerminal : nonTerminals) {
			nonTerminal.addStarters(getStarters(nonTerminal));
		}
	}

	private LinkedList<Terminal> getStarters(Term term) {
		// Als term een Terminal of Empty is dan opslaan en overslaan
		if(term instanceof Terminal) {
			LinkedList<Terminal> terminals = new LinkedList<Terminal>();
			terminals.add((Terminal) term);
			return terminals;
		}

		LinkedList<Terminal> terminals = new LinkedList<Terminal>();

		NonTerminal nonTerminal = (NonTerminal) term;
		if(nonTerminal.getStarters().size() == 0) {
			Rule rule = getRuleByRuleName(nonTerminal);
			// Loop door alle productie regels heen
			for(ProductionLine productionLine : rule.getProductionLines()) {
				boolean hasEmpty = false;
				// Haal alle terms van de productie regel op en loop er doorheen
				LinkedList<Term> terms = productionLine.getTerms();
				for(Term ruleTerm : terms) {
					hasEmpty = false;
					
					// Als de Term gelijk is aan de rule naam dan slaan we deze over omdat we anders
					// in een oneindige recursieve loop terecht komen
					if(nonTerminal.getTerm().equals(ruleTerm.getTerm())) {
						continue;
					}
					
					// Haal een lijst op van alle terminals van de huidige NonTerminal
					for(Terminal terminal : getStarters(ruleTerm)) {
						// We bepalen de starters set van elke NonTerminal, als we een EMPTY
						// tegen komen dan noteren we dat zodat we de starters set van de terminal
						// na de EMPTY ook kunnen gaan bepalen (indien nodig)
						if(terminal instanceof Empty) {
							hasEmpty = true;
						} else {
							terminals.add(terminal);
						}
					}

					if(!hasEmpty) {
						break;
					}
				}

				// Als hasEmpty true is dan bevat elke starter een EMPTY dus kan de hele productie regel
				// ook EMPTY zijn.
				if(hasEmpty) {
					terminals.add(new Empty("ε"));
				}
			}
		}

		return terminals;
	}

	private void computeFollowers() {
		for(NonTerminal nonTerminal : nonTerminals) {
			if(nonTerminal.getTerm().equals(startNonTerminal.getTerm())) {
				nonTerminal.addFollowers(getFollowers(nonTerminal));
				nonTerminal.addFollower(new EOF("$"));
			} else {
				nonTerminal.addFollowers(getFollowers(nonTerminal));
			}
		}
	}

	private LinkedList<Terminal> getFollowers(NonTerminal nonTerminal) {		
		LinkedList<Terminal> terminals = new LinkedList<Terminal>();

		if(nonTerminal.getFollowers().size() == 0) {
			boolean foundTerm = false;

			for(Rule rule : rules.values()) {
				for(ProductionLine productionLine : rule.getProductionLines()) {
					LinkedList<Term> terms = productionLine.getTerms();

					int index = 0;

					do {
						if(nonTerminal.getTerm().equals(terms.get(index).getTerm())) {
							foundTerm = true;

							if((index + 1) < terms.size()) {
								for(int i = (index + 1); i < terms.size(); i++) {

									Term nextTerm = terms.get(i);
									if(nextTerm instanceof Terminal) {
										terminals.add((Terminal) nextTerm);
										break;
									} else if(nextTerm instanceof NonTerminal) {
										NonTerminal nextNonTerminal = compareNonTerminals((NonTerminal) nextTerm);

										List<Terminal> starters = new LinkedList<Terminal>();
										starters.addAll(nextNonTerminal.getStarters());
										
										boolean foundEmpty = false;
										for(Terminal terminal : starters) {
											if(terminal instanceof Empty) {
												foundEmpty = true;
												
												// We hebben nu de empty gevonden, dus moeten we die verwijderen
												// want er mogen geen empty terminals in de followers lijst
												starters.remove(terminal);
												break;
											}
										}

										if(foundEmpty) {
											terminals.addAll(starters);
											
											// Als er empty word gevonden maar we zitten al in de laatste term van de rule
											// dan hebben we een probleem dat er niet verder word gekeken. // De index + 2 is
											// dus om te kijken of we aan het einde zijn, want dan moeten we de followers
											// van de regel zelf pakken.
											if((index + 2) == terms.size()) {
												NonTerminal ruleNonTerminal = compareNonTerminals(rule.getRuleName());
												
												// Doorheen lopen om te zorgen dat we geen dubbele toevoegen.
												for(Terminal follower : ruleNonTerminal.getFollowers()) {
													if(!terminals.contains(follower)) {
														terminals.add(follower);
													}
												}
											}
										} else {
											terminals.addAll(starters);
											break;
										}
									}
								}
							} else {
								NonTerminal ruleNonTerminal = compareNonTerminals(rule.getRuleName());								
								terminals.addAll(ruleNonTerminal.getFollowers());
							}
						}

						index++;
					} while(!foundTerm && index < terms.size());

					if(foundTerm) {
						break;
					}
				}
			}
		}

		return terminals;
	}

	private NonTerminal compareNonTerminals(NonTerminal compareTo) {
		boolean found = false;
		NonTerminal foundNonTerminal = null;

		int index = 0;
		while(!found) {
			if(nonTerminals.get(index).getTerm().equals(compareTo.getTerm())) {
				found = true;
				foundNonTerminal = nonTerminals.get(index);
			}
			index++;
		}

		return foundNonTerminal;
	}

	public Rule getRuleByRuleName(NonTerminal ruleName) {
		int i = 0;
		Rule rule = null;
		boolean found = false;

		while(!found && i < rules.size()) {
			if(rules.get(ruleName.getTerm()) != null) {
				found = true;
				rule = rules.get(ruleName.getTerm());
			}

			i++;
		}
		return rule;
	}
	
	private void showStarters() {
		String starters = "";
		for(NonTerminal nonTerminal : nonTerminals) {
			starters += "Starters van : "+ nonTerminal +" : {";
		
			for(Terminal terminal : nonTerminal.getStarters()) {
				starters += terminal +", ";
			}
			
			if(starters.endsWith(", ")) {
				starters = starters.substring(0, starters.length() - 2);
			}
			
			starters += "}\n";
		}
		
		System.out.println(starters);
	}
	
	private void showFollowers() {
		String followers = "";
		for(NonTerminal nonTerminal : nonTerminals) {
			followers += "Followers van : "+ nonTerminal +" : {";
		
			for(Terminal terminal : nonTerminal.getFollowers()) {
				followers += terminal +", ";
			}
			
			if(followers.endsWith(", ")) {
				followers = followers.substring(0, followers.length() - 2);
			}
			
			followers += "}\n";
		}

		System.out.println(followers);
	}
	
	public void checkStarterStarterConflict() {
		for(NonTerminal nonTerminal : nonTerminals) {
			List<Terminal> starters = new LinkedList<Terminal>();
			
			for(Terminal terminal : nonTerminal.getStarters()) {
				if(!starters.contains(terminal)) {
					starters.add(terminal);
				} else {
					conflict1 = true;
					conflict1Message = "Starter '"+ terminal +"' komt vaker in de starters van '"+ nonTerminal +"' voor.";
					break;
				}
			}
		}
	}
	
	public void checkStarterFollowConflict() {
		for(NonTerminal nonTerminal : nonTerminals) {
			List<Terminal> starters = nonTerminal.getStarters();
			List<Terminal> followers = nonTerminal.getFollowers();

			// .equals en .contains gaven steeds false terug, daarom is dit met een
			// loop gedaan
			boolean hasEmpty = false;
			for(Terminal terminal : starters) {
				if(terminal instanceof Empty) {
					hasEmpty = true;
					break;
				}
			}
			
			if(hasEmpty) {
				for(Terminal starter : starters) {
					if(followers.contains(starter)) {
						conflict2 = true;
						conflict2Message = "Terminal '"+ starter +"' komt zowel in de starters en followers voor van '"+ nonTerminal +"'.";
					}
				}
			}
		}
	}
	
	public void checkLeftRecursiveConflict() {
		for(Rule rule : rules.values()) {
			for(ProductionLine productionLine : rule.getProductionLines()) {
				if(productionLine.getFirstTerm().getTerm().equals(rule.getRuleName().getTerm())) {
					conflict3 = true;
					conflict3Message = "Links recursief in rule '"+ rule.getRuleName() + "'.";
				}
			}
		}
	}
}
